ปลดล็อกความมหัศจรรย์เบื้องหลังประสิทธิภาพของ React คู่มือฉบับสมบูรณ์นี้จะอธิบายอัลกอริทึม Reconciliation, Virtual DOM diffing และกลยุทธ์การเพิ่มประสิทธิภาพที่สำคัญ
สูตรลับของ React: เจาะลึก Reconciliation Algorithm และ Virtual DOM Diffing
ในโลกของการพัฒนาเว็บสมัยใหม่ React ได้สร้างตัวเองขึ้นมาเป็นกำลังสำคัญในการสร้างส่วนต่อประสานผู้ใช้ (UI) ที่ไดนามิกและโต้ตอบได้ ความนิยมของมันไม่ได้มาจากสถาปัตยกรรมแบบคอมโพเนนต์เท่านั้น แต่ยังมาจากประสิทธิภาพที่โดดเด่นอีกด้วย แต่อะไรที่ทำให้ React เร็วขนาดนั้น? คำตอบไม่ใช่เวทมนตร์ แต่เป็นผลงานทางวิศวกรรมอันยอดเยี่ยมที่รู้จักกันในชื่อ Reconciliation algorithm
สำหรับนักพัฒนาหลายคน การทำงานภายในของ React เปรียบเสมือนกล่องดำ เราเขียนคอมโพเนนต์ จัดการ state และเฝ้าดู UI อัปเดตอย่างไม่มีที่ติ อย่างไรก็ตาม การทำความเข้าใจกลไกเบื้องหลังกระบวนการที่ราบรื่นนี้ โดยเฉพาะอย่างยิ่ง Virtual DOM และอัลกอริทึม diffing ของมัน คือสิ่งที่แยกระหว่างนักพัฒนา React ที่ดีออกจากนักพัฒนาที่ยอดเยี่ยม ความรู้เชิงลึกนี้จะช่วยให้คุณสามารถเขียนแอปพลิเคชันที่มีประสิทธิภาพสูงสุด แก้ปัญหาคอขวดด้านประสิทธิภาพ และเชี่ยวชาญไลบรารีได้อย่างแท้จริง
คู่มือฉบับสมบูรณ์นี้จะไขความกระจ่างเกี่ยวกับกระบวนการเรนเดอร์หลักของ React เราจะสำรวจว่าทำไมการจัดการ DOM โดยตรงจึงมีค่าใช้จ่ายสูง, Virtual DOM ให้ทางออกที่สวยงามได้อย่างไร และ Reconciliation algorithm อัปเดต UI ของคุณอย่างมีประสิทธิภาพได้อย่างไร นอกจากนี้ เรายังจะเจาะลึกถึงวิวัฒนาการจาก Stack Reconciler ดั้งเดิมไปสู่ Fiber Architecture สมัยใหม่ และสรุปด้วยกลยุทธ์ที่นำไปปฏิบัติได้จริงซึ่งคุณสามารถนำไปใช้เพื่อเพิ่มประสิทธิภาพแอปพลิเคชันของคุณเองได้ตั้งแต่วันนี้
ปัญหาหลัก: ทำไมการจัดการ DOM โดยตรงจึงไม่มีประสิทธิภาพ
เพื่อให้เข้าใจถึงโซลูชันของ React เราต้องเข้าใจปัญหาก่อน Document Object Model (DOM) คือ API ของเบราว์เซอร์สำหรับแสดงผลและโต้ตอบกับเอกสาร HTML มันมีโครงสร้างเป็นต้นไม้ของอ็อบเจกต์ โดยแต่ละโหนด (node) จะแทนส่วนต่างๆ ของเอกสาร (เช่น element, text หรือ attribute)
เมื่อคุณต้องการเปลี่ยนแปลงสิ่งที่อยู่บนหน้าจอ คุณต้องจัดการกับ DOM tree นี้ ตัวอย่างเช่น หากต้องการเพิ่มรายการใหม่ในลิสต์ คุณต้องสร้าง element <li> ใหม่และผนวกเข้ากับโหนด <ul> แม้จะดูตรงไปตรงมา แต่การดำเนินการกับ DOM นั้นมีค่าใช้จ่ายในการคำนวณสูง นี่คือเหตุผล:
- Layout and Reflow: เมื่อใดก็ตามที่คุณเปลี่ยนแปลงรูปทรงของ element (เช่น ความกว้าง, ความสูง หรือตำแหน่ง) เบราว์เซอร์จะต้องคำนวณตำแหน่งและขนาดของ elements ทั้งหมดที่ได้รับผลกระทบใหม่ กระบวนการนี้เรียกว่า "reflow" หรือ "layout" และอาจส่งผลกระทบต่อเนื่องไปทั่วทั้งเอกสาร ซึ่งใช้พลังการประมวลผลอย่างมาก
- Repainting: หลังจาก reflow เบราว์เซอร์จำเป็นต้องวาดพิกเซลบนหน้าจอสำหรับ elements ที่อัปเดตใหม่ ซึ่งเรียกว่า "repainting" หรือ "rasterizing" การเปลี่ยนแปลงสิ่งที่ง่ายๆ เช่น สีพื้นหลังอาจกระตุ้นแค่ repaint แต่การเปลี่ยนแปลง layout จะกระตุ้น repaint เสมอ
- Synchronous and Blocking: การดำเนินการกับ DOM เป็นแบบซิงโครนัส (synchronous) เมื่อโค้ด JavaScript ของคุณแก้ไข DOM เบราว์เซอร์มักจะต้องหยุดงานอื่นๆ ชั่วคราว รวมถึงการตอบสนองต่อการป้อนข้อมูลของผู้ใช้ เพื่อดำเนินการ reflow และ repaint ซึ่งอาจนำไปสู่ UI ที่ช้าหรือค้างได้
ลองนึกภาพแอปพลิเคชันที่ซับซ้อนซึ่งมีโหนดหลายพันโหนด หากคุณอัปเดต state และเรนเดอร์ UI ทั้งหมดใหม่โดยการจัดการ DOM โดยตรง คุณกำลังบังคับให้เบราว์เซอร์เข้าสู่กระบวนการ reflows และ repaints ที่มีค่าใช้จ่ายสูงอย่างต่อเนื่อง ซึ่งส่งผลให้ประสบการณ์ผู้ใช้ที่แย่มาก
ทางออก: The Virtual DOM (VDOM)
ผู้สร้าง React ตระหนักถึงปัญหาคอขวดด้านประสิทธิภาพของการจัดการ DOM โดยตรง ทางออกของพวกเขาคือการแนะนำชั้นของ abstraction ขึ้นมา นั่นคือ Virtual DOM
Virtual DOM คืออะไร?
Virtual DOM (VDOM) คือตัวแทนของ DOM จริงที่อยู่ในหน่วยความจำและมีน้ำหนักเบา โดยพื้นฐานแล้วมันคืออ็อบเจกต์ JavaScript ธรรมดาที่อธิบาย UI อ็อบเจกต์ VDOM มีคุณสมบัติที่สะท้อนถึง attributes ของ DOM element จริง ตัวอย่างเช่น <div> แบบง่ายๆ อาจแสดงได้ดังนี้:
{ type: 'div', props: { className: 'container', children: 'Hello World' } }
เนื่องจากสิ่งเหล่านี้เป็นเพียงอ็อบเจกต์ JavaScript การสร้างและจัดการจึงรวดเร็วอย่างไม่น่าเชื่อ มันไม่เกี่ยวข้องกับการโต้ตอบกับ API ของเบราว์เซอร์เลย ดังนั้นจึงไม่มี reflows หรือ repaints
Virtual DOM ทำงานอย่างไร?
VDOM ช่วยให้สามารถพัฒนา UI ในรูปแบบ declarative ได้ แทนที่จะบอกเบราว์เซอร์ว่าจะเปลี่ยน DOM อย่างไรทีละขั้นตอน (imperative) คุณเพียงแค่ประกาศว่า UI ควรมีหน้าตาเป็นอย่างไรสำหรับ state ที่กำหนด (declarative) แล้ว React จะจัดการส่วนที่เหลือเอง
กระบวนการมีลักษณะดังนี้:
- Initial Render: เมื่อแอปพลิเคชันของคุณโหลดครั้งแรก React จะสร้าง Virtual DOM tree ที่สมบูรณ์สำหรับ UI ของคุณและใช้มันเพื่อสร้าง DOM จริงเริ่มต้น
- State Update: เมื่อ state ของแอปพลิเคชันเปลี่ยนแปลง (เช่น ผู้ใช้คลิกปุ่ม) React จะสร้าง Virtual DOM tree ใหม่ที่สะท้อนถึง state ใหม่นั้น
- Diffing: ตอนนี้ React มี Virtual DOM trees สองชุดในหน่วยความจำ: ชุดเก่า (ก่อนการเปลี่ยนแปลง state) และชุดใหม่ จากนั้นมันจะใช้อัลกอริทึม "diffing" เพื่อเปรียบเทียบต้นไม้ทั้งสองนี้และระบุความแตกต่างที่แท้จริง
- Batching and Updating: React จะคำนวณชุดคำสั่งที่มีประสิทธิภาพและน้อยที่สุดที่จำเป็นในการอัปเดต DOM จริงให้ตรงกับ Virtual DOM ใหม่ คำสั่งเหล่านี้จะถูกรวบรวม (batch) และนำไปใช้กับ DOM จริงในลำดับเดียวที่ถูกปรับให้เหมาะสมที่สุด
ด้วยการรวบรวมการอัปเดต React จะลดการโต้ตอบโดยตรงกับ DOM ที่ช้า ซึ่งช่วยเพิ่มประสิทธิภาพได้อย่างมาก หัวใจของประสิทธิภาพนี้อยู่ที่ขั้นตอน "diffing" ซึ่งเรียกอย่างเป็นทางการว่า Reconciliation algorithm
หัวใจของ React: The Reconciliation Algorithm
Reconciliation คือกระบวนการที่ React ใช้ในการอัปเดต DOM ให้ตรงกับ component tree ล่าสุด อัลกอริทึมที่ทำการเปรียบเทียบนี้คือสิ่งที่เราเรียกว่า "diffing algorithm"
ในทางทฤษฎี การค้นหาจำนวนการเปลี่ยนแปลงที่น้อยที่สุดเพื่อแปลงต้นไม้หนึ่งไปเป็นอีกต้นไม้หนึ่งเป็นปัญหาที่ซับซ้อนมาก โดยมีความซับซ้อนของอัลกอริทึมในระดับ O(n³ โดยที่ n คือจำนวนโหนดในต้นไม้ ซึ่งจะช้าเกินไปสำหรับแอปพลิเคชันในโลกแห่งความเป็นจริง เพื่อแก้ปัญหานี้ ทีมงานของ React ได้ทำการสังเกตที่ยอดเยี่ยมเกี่ยวกับพฤติกรรมของเว็บแอปพลิเคชันโดยทั่วไป และได้สร้างอัลกอริทึมแบบ heuristic ที่เร็วกว่ามาก โดยทำงานในเวลา O(n)
The Heuristics: ทำให้ Diffing รวดเร็วและคาดเดาได้
อัลกอริทึม diffing ของ React สร้างขึ้นจากสมมติฐานหรือ heuristics หลักสองข้อ:
Heuristic 1: Element Types ที่แตกต่างกันจะสร้าง Trees ที่แตกต่างกัน
นี่เป็นกฎข้อแรกและตรงไปตรงมาที่สุด เมื่อเปรียบเทียบโหนด VDOM สองโหนด React จะดูที่ type ของมันก่อน หาก type ของ root elements แตกต่างกัน React จะสันนิษฐานว่านักพัฒนาไม่ต้องการพยายามแปลงอันหนึ่งไปเป็นอีกอันหนึ่ง แต่มันจะใช้วิธีที่รุนแรงกว่าแต่คาดเดาได้:
- มันจะรื้อ tree เก่าทั้งหมดทิ้ง, unmount คอมโพเนนต์เก่าทั้งหมด และทำลาย state ของมัน
- มันจะสร้าง tree ใหม่ทั้งหมดจากศูนย์ตาม element type ใหม่
ตัวอย่างเช่น พิจารณาการเปลี่ยนแปลงนี้:
ก่อน: <div><Counter /></div>
หลัง: <span><Counter /></span>
แม้ว่าคอมโพเนนต์ลูก Counter จะเหมือนเดิม แต่ React เห็นว่า root ได้เปลี่ยนจาก div เป็น span มันจะ unmount div เก่าและ instance ของ Counter ที่อยู่ภายในทั้งหมด (ทำให้สูญเสีย state) จากนั้นจะ mount span ใหม่และ instance ใหม่ของ Counter
ข้อควรรู้: หลีกเลี่ยงการเปลี่ยน root element type ของ subtree ของคอมโพเนนต์ หากคุณต้องการรักษาสถานะของมันไว้ หรือหลีกเลี่ยงการ re-render ทั้งหมดของ subtree นั้น
Heuristic 2: นักพัฒนาสามารถบอกใบ้ถึง Elements ที่คงที่ได้ด้วย key Prop
นี่อาจเป็น heuristic ที่สำคัญที่สุดสำหรับนักพัฒนาที่ต้องทำความเข้าใจและนำไปใช้อย่างถูกต้อง เมื่อ React เปรียบเทียบรายการของ child elements พฤติกรรมเริ่มต้นของมันคือการวนซ้ำไปตามรายการของ children ทั้งสองพร้อมกันและสร้าง mutation เมื่อใดก็ตามที่พบความแตกต่าง
ปัญหาของการ Diffing โดยใช้ Index
ลองนึกภาพว่าเรามีรายการของ items และเราเพิ่ม item ใหม่เข้าไปที่จุดเริ่มต้นของรายการโดยไม่ได้ใช้ keys
รายการเริ่มต้น:
- Item B
- Item C
รายการที่อัปเดต (เพิ่ม 'Item A' ที่ตอนต้น):
- Item A
- Item B
- Item C
หากไม่มี keys, React จะทำการเปรียบเทียบตาม index อย่างง่าย:
- มันเปรียบเทียบ item เก่าที่ index 0 ('Item B') กับ item ใหม่ที่ index 0 ('Item A') ทั้งสองต่างกัน ดังนั้นมันจึง mutate item แรก
- มันเปรียบเทียบ item เก่าที่ index 1 ('Item C') กับ item ใหม่ที่ index 1 ('Item B') ทั้งสองต่างกัน ดังนั้นมันจึง mutate item ที่สอง
- มันเห็นว่ามี item ใหม่ที่ index 2 ('Item C') และทำการเพิ่มเข้าไป
นี่เป็นวิธีที่ไม่มีประสิทธิภาพอย่างมาก React ได้ทำการ mutate ที่ไม่จำเป็นสองครั้งและการเพิ่มหนึ่งครั้ง ทั้งที่สิ่งที่ต้องการมีเพียงการเพิ่มรายการเดียวที่จุดเริ่มต้น หากรายการเหล่านี้เป็นคอมโพเนนต์ที่ซับซ้อนและมี state ของตัวเอง อาจนำไปสู่ปัญหาด้านประสิทธิภาพและบั๊กที่ร้ายแรง เนื่องจาก state อาจสับสนระหว่างคอมโพเนนต์ได้
พลังของ key Prop
key prop คือทางออก มันเป็น string attribute พิเศษที่คุณต้องใส่เข้าไปเมื่อสร้างรายการของ elements Keys ช่วยให้ React มีตัวตนที่คงที่สำหรับแต่ละ element
กลับไปที่ตัวอย่างเดิม แต่คราวนี้ใช้ keys ที่เสถียรและไม่ซ้ำกัน:
รายการเริ่มต้น:
- Item B
- Item C
รายการที่อัปเดต:
- Item A
- Item B
- Item C
ตอนนี้ กระบวนการ diffing ของ React ฉลาดขึ้นมาก:
- React มองไปที่ children ของรายการใหม่และพบ elements ที่มี keys 'b' และ 'c'
- มันรู้ว่า elements ที่มี keys 'b' และ 'c' มีอยู่แล้วในรายการเก่า ดังนั้นมันจึงแค่ย้ายตำแหน่ง
- มันเห็นว่ามี element ใหม่ที่มี key 'a' ซึ่งไม่เคยมีมาก่อน ดังนั้นมันจึงสร้างและเพิ่มเข้าไป
นี่มีประสิทธิภาพมากกว่ามาก React ระบุได้อย่างถูกต้องว่าต้องการเพียงการเพิ่มแค่ครั้งเดียว คอมโพเนนต์ที่เกี่ยวข้องกับ keys 'b' และ 'c' จะถูกรักษาไว้ ทำให้คง state ภายในของมันได้
กฎสำคัญสำหรับ Keys: Keys จะต้อง เสถียร, คาดเดาได้ และไม่ซ้ำกัน ในบรรดา elements ที่อยู่ระดับเดียวกัน การใช้ index ของ array เป็น key (items.map((item, index) => ) ถือเป็น anti-pattern หาก list นั้นมีการจัดลำดับใหม่, กรอง, หรือมีการเพิ่ม/ลบรายการตรงกลาง เพราะจะนำไปสู่ปัญหาเดียวกันกับการไม่มี key เลย Key ที่ดีที่สุดคือตัวระบุที่ไม่ซ้ำกันจากข้อมูลของคุณ เช่น ID จากฐานข้อมูล
วิวัฒนาการ: จาก Stack สู่ Fiber Architecture
อัลกอริทึม reconciliation ที่อธิบายข้างต้นเป็นรากฐานของ React มาหลายปี อย่างไรก็ตาม มันมีข้อจำกัดที่สำคัญอย่างหนึ่ง: มันเป็นแบบ synchronous และ blocking การนำไปใช้แบบดั้งเดิมนี้ ปัจจุบันถูกเรียกว่า Stack Reconciler
วิธีเก่า: The Stack Reconciler
ใน Stack Reconciler เมื่อการอัปเดต state ทำให้เกิดการ re-render, React จะทำการ traverse component tree ทั้งหมดแบบเรียกซ้ำ (recursively), คำนวณการเปลี่ยนแปลง, และนำไปใช้กับ DOM—ทั้งหมดนี้เกิดขึ้นในลำดับเดียวที่ไม่สามารถขัดจังหวะได้ สำหรับการอัปเดตเล็กๆ น้อยๆ ก็ไม่เป็นไร แต่สำหรับ component trees ขนาดใหญ่ กระบวนการนี้อาจใช้เวลานาน (เช่น มากกว่า 16ms) ซึ่งจะบล็อก main thread ของเบราว์เซอร์ ซึ่งจะทำให้ UI ไม่ตอบสนอง, เกิดเฟรมตก, แอนิเมชันกระตุก และประสบการณ์ผู้ใช้ที่ไม่ดี
ขอแนะนำ React Fiber (React 16+)
เพื่อแก้ปัญหานี้ ทีม React ได้ดำเนินโครงการหลายปีเพื่อเขียนอัลกอริทึม reconciliation หลักใหม่ทั้งหมด ผลลัพธ์ที่เปิดตัวใน React 16 เรียกว่า React Fiber
Fiber Architecture ถูกออกแบบมาตั้งแต่ต้นเพื่อเปิดใช้งาน concurrency—ความสามารถของ React ในการทำงานหลายอย่างพร้อมกันและสลับไปมาระหว่างงานเหล่านั้นตามลำดับความสำคัญ
"Fiber" คืออ็อบเจกต์ JavaScript ธรรมดาที่แทนหน่วยของงาน (unit of work) มันเก็บข้อมูลเกี่ยวกับคอมโพเนนต์, อินพุต (props), และเอาต์พุต (children) แทนที่จะเป็นการ traverse แบบเรียกซ้ำที่ไม่สามารถขัดจังหวะได้ ตอนนี้ React ประมวลผล linked list ของ fiber nodes ทีละโหนด
สถาปัตยกรรมใหม่นี้ปลดล็อกความสามารถหลักหลายอย่าง:
- Incremental Rendering: สามารถแบ่งงานเรนเดอร์ออกเป็นส่วนเล็กๆ และกระจายออกไปหลายเฟรม
- Prioritization: สามารถกำหนดระดับความสำคัญที่แตกต่างกันให้กับการอัปเดตประเภทต่างๆ ตัวอย่างเช่น การพิมพ์ของผู้ใช้ในช่อง input มีความสำคัญสูงกว่าข้อมูลที่กำลังถูกดึงมาจากเบื้องหลัง
- Pausability and Abortability: สามารถหยุดงานในการอัปเดตที่มีความสำคัญต่ำเพื่อจัดการกับงานที่มีความสำคัญสูง และยังสามารถยกเลิกหรือนำงานที่ไม่จำเป็นแล้วกลับมาใช้ใหม่ได้
สองเฟสของ Fiber
ภายใต้ Fiber กระบวนการเรนเดอร์แบ่งออกเป็นสองเฟสที่แตกต่างกัน:
- The Render/Reconciliation Phase (Asynchronous): ในเฟสนี้ React จะประมวลผล fiber nodes เพื่อสร้าง "work-in-progress" tree มันจะเรียกเมธอด
renderของคอมโพเนนต์และใช้อัลกอริทึม diffing เพื่อตัดสินใจว่าต้องทำการเปลี่ยนแปลงอะไรกับ DOM บ้าง ที่สำคัญคือ เฟสนี้สามารถขัดจังหวะได้ React สามารถหยุดงานนี้ชั่วคราวเพื่อจัดการกับสิ่งที่สำคัญกว่า และกลับมาทำต่อในภายหลัง เนื่องจากสามารถถูกขัดจังหวะได้ React จะไม่ทำการเปลี่ยนแปลง DOM จริงใดๆ ในระหว่างเฟสนี้เพื่อหลีกเลี่ยงสถานะ UI ที่ไม่สอดคล้องกัน - The Commit Phase (Synchronous): เมื่อ work-in-progress tree เสร็จสมบูรณ์ React จะเข้าสู่ commit phase มันจะนำการเปลี่ยนแปลงที่คำนวณไว้ไปใช้กับ DOM จริง เฟสนี้เป็นแบบซิงโครนัสและไม่สามารถขัดจังหวะได้ เพื่อให้แน่ใจว่าผู้ใช้จะเห็น UI ที่สอดคล้องกันเสมอ เมธอด Lifecycle เช่น
componentDidMountและcomponentDidUpdateรวมถึง hooksuseLayoutEffectและuseEffectจะถูกเรียกใช้งานในระหว่างเฟสนี้
Fiber Architecture เป็นรากฐานสำหรับฟีเจอร์สมัยใหม่หลายอย่างของ React รวมถึง Suspense, concurrent rendering, useTransition และ useDeferredValue ซึ่งทั้งหมดนี้ช่วยให้นักพัฒนาสร้างส่วนต่อประสานผู้ใช้ที่ตอบสนองและลื่นไหลมากขึ้น
กลยุทธ์การเพิ่มประสิทธิภาพเชิงปฏิบัติสำหรับนักพัฒนา
การทำความเข้าใจกระบวนการ reconciliation ของ React ช่วยให้คุณสามารถเขียนโค้ดที่มีประสิทธิภาพมากขึ้นได้ นี่คือกลยุทธ์ที่นำไปปฏิบัติได้จริง:
1. ใช้ Keys ที่เสถียรและไม่ซ้ำกันสำหรับ Lists เสมอ
เรื่องนี้ไม่สามารถเน้นย้ำได้มากพอ มันคือการเพิ่มประสิทธิภาพที่สำคัญที่สุดเพียงอย่างเดียวสำหรับ lists ใช้ ID ที่ไม่ซ้ำกันจากข้อมูลของคุณ (เช่น product.id) หลีกเลี่ยงการใช้ array indices เว้นแต่ว่า list นั้นเป็นแบบ static อย่างสมบูรณ์และจะไม่มีการเปลี่ยนแปลง
2. หลีกเลี่ยงการ Re-render ที่ไม่จำเป็น
คอมโพเนนต์จะ re-render หาก state ของมันเปลี่ยนแปลงหรือ parent ของมัน re-render บางครั้ง คอมโพเนนต์ re-render แม้ว่าผลลัพธ์ของมันจะเหมือนเดิมทุกประการ คุณสามารถป้องกันสิ่งนี้ได้โดยใช้:
React.memo(): A higher-order component for function components. It performs a shallow comparison of the component's props. If the props haven't changed, React will skip re-rendering the component and reuse the last rendered result.useCallback(): ฟังก์ชันที่กำหนดภายในคอมโพเนนต์จะถูกสร้างขึ้นใหม่ทุกครั้งที่มีการเรนเดอร์ หากคุณส่งฟังก์ชันเหล่านี้ลงไปเป็น props ให้กับ child component ที่หุ้มด้วยReact.memo, child จะ re-render เพราะ prop ที่เป็นฟังก์ชันนั้นทางเทคนิคแล้วเป็นฟังก์ชันใหม่ทุกครั้งuseCallbackจะ memoize ตัวฟังก์ชันเอง เพื่อให้แน่ใจว่ามันจะถูกสร้างขึ้นใหม่ก็ต่อเมื่อ dependencies ของมันเปลี่ยนแปลงเท่านั้นuseMemo(): คล้ายกับuseCallbackแต่ใช้สำหรับค่า มันจะ memoize ผลลัพธ์ของการคำนวณที่มีค่าใช้จ่ายสูง การคำนวณจะทำงานอีกครั้งก็ต่อเมื่อ dependencies ตัวใดตัวหนึ่งของมันเปลี่ยนแปลง สิ่งนี้มีประโยชน์ในการป้องกันการคำนวณที่สิ้นเปลืองในทุกๆ การเรนเดอร์ และเพื่อรักษา object/array references ที่เสถียรซึ่งส่งเป็น props
3. การจัดองค์ประกอบคอมโพเนนต์อย่างชาญฉลาด
วิธีที่คุณจัดโครงสร้างคอมโพเนนต์ของคุณอาจมีผลกระทบอย่างมีนัยสำคัญต่อประสิทธิภาพ หากส่วนหนึ่งของ state ของคอมโพเนนต์ของคุณอัปเดตบ่อยครั้ง พยายามแยกมันออกจากส่วนที่ไม่อัปเดต
ตัวอย่างเช่น แทนที่จะมีคอมโพเนนต์ขนาดใหญ่เพียงชิ้นเดียวซึ่งช่อง input ที่เปลี่ยนแปลงบ่อยทำให้ทั้งคอมโพเนนต์ต้อง re-render, ให้ย้าย state นั้นไปไว้ในคอมโพเนนต์ย่อยของตัวเอง ด้วยวิธีนี้ จะมีเพียงคอมโพเนนต์ขนาดเล็กเท่านั้นที่ re-render เมื่อผู้ใช้พิมพ์
4. ใช้ Virtualize สำหรับ Lists ที่ยาวมาก
หากคุณต้องการเรนเดอร์ lists ที่มีรายการหลายร้อยหรือหลายพันรายการ แม้จะใช้ keys ที่เหมาะสม การเรนเดอร์ทั้งหมดในครั้งเดียวอาจช้าและใช้หน่วยความจำจำนวนมาก ทางออกคือ virtualization หรือ windowing เทคนิคนี้เกี่ยวข้องกับการเรนเดอร์เฉพาะส่วนย่อยของรายการที่มองเห็นได้ใน viewport เท่านั้น เมื่อผู้ใช้เลื่อน รายการเก่าจะถูก unmount และรายการใหม่จะถูก mount ไลบรารีเช่น react-window และ react-virtualized มีคอมโพเนนต์ที่มีประสิทธิภาพและใช้งานง่ายสำหรับนำรูปแบบนี้ไปใช้
สรุป
ประสิทธิภาพของ React ไม่ใช่เรื่องบังเอิญ แต่เป็นผลมาจากสถาปัตยกรรมที่ตั้งใจและซับซ้อนซึ่งมีศูนย์กลางอยู่ที่ Virtual DOM และอัลกอริทึม Reconciliation ที่มีประสิทธิภาพ ด้วยการลดความซับซ้อนของการจัดการ DOM โดยตรง React สามารถรวบรวมและเพิ่มประสิทธิภาพการอัปเดตในแบบที่ซับซ้อนอย่างยิ่งหากต้องจัดการด้วยตนเอง
ในฐานะนักพัฒนา เราเป็นส่วนสำคัญของกระบวนการนี้ ด้วยการทำความเข้าใจ heuristics ของอัลกอริทึม diffing—การใช้ keys อย่างเหมาะสม, การ memoize คอมโพเนนต์และค่า, และการจัดโครงสร้างแอปพลิเคชันของเราอย่างรอบคอบ—เราสามารถทำงานร่วมกับ reconciler ของ React ได้ ไม่ใช่ต่อต้านมัน วิวัฒนาการสู่สถาปัตยกรรม Fiber ได้ผลักดันขอบเขตของสิ่งที่เป็นไปได้ให้ไกลยิ่งขึ้น ทำให้เกิด UIs รุ่นใหม่ที่ลื่นไหลและตอบสนองได้ดี
ครั้งต่อไปที่คุณเห็น UI ของคุณอัปเดตทันทีหลังจากการเปลี่ยนแปลง state ลองใช้เวลาสักครู่เพื่อชื่นชมการทำงานร่วมกันอันสวยงามของ Virtual DOM, อัลกอริทึม diffing และ commit phase ที่เกิดขึ้นอยู่เบื้องหลัง ความเข้าใจนี้คือกุญแจสำคัญของคุณในการสร้างแอปพลิเคชัน React ที่เร็วขึ้น, มีประสิทธิภาพมากขึ้น และแข็งแกร่งขึ้นสำหรับผู้ชมทั่วโลก